# Symbol extensions
class Symbol
  # Returns the symbol as a header string
  def to_header
    return @to_header if @to_header
    s = to_s
    @to_header = (s.upcase == s) ? s :
      to_s.split('_').map{|w| w.capitalize}.join('-')
  end
end

class String
  alias_method :to_header, :to_s
end

module BulkMail
  # The Message class can be used to compose email messages with headers. Usage
  # is pretty straightforward:
  #
  #   m = BulkMail::Message.new({:from => my_email, :subject => 'hello'},
  #     'Hi there!')
  class Message
    attr_accessor :headers, :body

    DEFAULT_HEADERS = {
      :mime_version => '1.0'
    }
    
    PLAIN = 'text/plain; charset=UTF-8'.freeze
    HTML = 'text/html; charset=UTF-8'.freeze
    
    # Creates a new message, including the default headers in the message headers.
    def initialize(headers, body)
      @headers = DEFAULT_HEADERS.merge(headers)
      @body = body
    end
    
    LINE_BREAK = "\r\n"
    HEADER_FMT = "%s: %s\r\n".freeze

    # Formats the message headers into a string.
    def self.format_headers(headers)
      headers.inject('') do |m, kv|
        if kv[1]
          m << (HEADER_FMT % [kv[0].to_header, kv[1]])
        else
          m
        end
      end
    end
    
    # Composes the header and body into a message string.
    def to_s
      msg = Message.format_headers(@headers)
      msg << LINE_BREAK
      msg << body
      msg
    end
  end
  
  # The MultiPart class can be used for composing multi-part messages. For
  # example:
  # 
  #   m = BulkMail::Message::MultiPart.new(
  #     {:from => 'my_email', :subject => 'Hello'}, :alternate)
  #   m.add_part({:content_type => BulkMail::Message::PLAIN},
  #     'You are now seeing the plain text version')
  #   m.add_part({:content_type => BulkMail::Message::HTML},
  #     '<p>You are now seeing the HTML version</p>')
  #   puts m.to_s
  class Message::MultiPart < Message
    attr_accessor :parts, :multi_type
    
    # Initializes a new instance.
    def initialize(headers, multi_type)
      super(headers, nil)
      @multi_type = multi_type
      @parts = []
    end
    
    # Adds a part to the message.
    def add_part(headers, body)
      @parts << Message.new(headers, body)
    end

    BOUNDARY = '!!@@##$$'.freeze
    MULTI_PART_CONTENT_TYPE_FMT = "multipart/%s; charset=UTF-8; boundary=\"#{BOUNDARY}\"".freeze
    BOUNDARY_LINE = "\r\n--#{BOUNDARY}\r\n".freeze
    
    # Composes the parts into an email message.
    def to_s
      @headers[:content_type] = MULTI_PART_CONTENT_TYPE_FMT % @multi_type
      msg = Message.format_headers(@headers)
      @parts.each do |part|
        msg << BOUNDARY_LINE
        msg << part.to_s
      end
      msg << BOUNDARY_LINE
      msg
    end
  end
end

